項目18:Don't panic
https://effective-rust.com/panic.html
「panic! を使うよりも、Result を使おう」
panic はプログラム中の回復不能なバグに対処するために設計されたもの
そのため、デフォルトでは panic したスレッドを停止させる
一見すると、std::panic::catch_unwind を使えば 例外 機構 をシミュレートできそうに思える
code:rs
fn divide(a: i64, b: i64) -> i64 {
if b == 0 {
panic!("Cowardly refusing to divide by zero!");
}
a / b
}
fn divide_recover(a: i64, b: i64, default: i64) -> i64 {
let result = std::panic::catch_unwind(|| divide(a, b));
match result {
Ok(x) => x,
Err(_) => default,
}
}
code:rs
let result = divide_recover(0, 0, 42);
println!("result = {result}");
上記のコードを動かすとシミュレートできているように見えるが、実際にはいくつか問題があるため避けたほうが良い
1. panic が常に アンワインド するとは限らない
コンパイラ オプションや Cargo.toml のプロファイル設定から、パニック時に即座にプロセスを中止(abort)することも可能
code:Cargo.toml
profile.release
panic = 'abort'
また、ビルドターゲット(e.g. WebAssembly)によっては、コンパイラやプロファイル設定によらず常に abort する
2. データ構造の処理中にパニックが起きたら、データ構造が整合した状態のままであることの保証がなくなる
いわゆる 例外安全性
Google が C++ のコード内で例外の使用を禁じた理由の 1 つ
https://google.github.io/styleguide/cppguide.html#Exceptions
3. パニックの伝搬は FFI 境界との相性が良くない(項目34:FFI境界を通過するものを制御しよう)
https://doc.rust-lang.org/nomicon/ffi.html#ffi-and-unwinding
「catch_unwind を使って Rust コード内で発生したパニックが、FFI 境界を超えて Rust 以外の言語で書かれた呼び出しコードに伝搬しないようにしよう」
代わりに、ライブラリコードでは Result を返すことでエラー処理を利用者に委ねるのが良い
panic の適切な使用例
ただし、例外的に panic を起こしても良いケースもある
main 関数内
エラーになることが非常に稀なので、その都度ユーザに unwrap 呼び出しをさせたくないケース
エラーが無効な入力値ではなく、内部データが破損しているために起こるケース
ただし、無効な入力値が通常の範囲を超えている場合、エントリポイント となる関数を 2 つ用意すると良い
1. 失敗しないパターン: シグネチャは常に成功することを示唆し、成功しなければ panic を起こす
e.g. String::from_utf8_unchecked
API ガイドライン で、インラインドキュメントの特定の節に、panic! することを記述するように定められている
項目27:パブリックインターフェイスのドキュメントを書こう
2. 失敗する可能性があるパターン: Result を返す
e.g. String::from_utf8
panic を回避するための注意点
上記のアドバイスを遵守するには、いくつか覚えておくべきことがある
panic は panic! 以外でも起きる
unwrap と unwrap_err
expect と expect_err
unreachable!
範囲外のインデックスでスライスを参照する(slice[index])
0 で除算する(x / y)
人間によるチェックでは不十分
代わりに、CI で panic が発生しうるコードを検出するのが良い
e.g. no_panic クレートを用いる
#Rust #Effective_Rust_―_Rustコードを改善し、エコシステムを最大限に活用するための35項目